/**
 *  @file  vsIniProfile.h version 1.0 2007-11-20 
 *  @brief Definition of class IniProfile.
 *  Copyright (c) 2007 by @author <abitfree@gmail.com>
 */

#ifndef __VS_INI_PROFILE_H__
#define __VS_INI_PROFILE_H__

#include <string>
#include <vector>
#include <utility>
#include <iostream>
#include <fstream>
#include "vsDefines.h"
#include "vsConstants.h"
#include "vsTypes.h"
#include "vsStringUtils.h"

namespace vs
{

/**
 * @brief A class for loading the INI format profile.
 *
 * This class can load and parse INI profiles easily.
 * The INI format profile is often composed of sections, which has some key-value pairs.
 */
class IniProfile
{
	typedef std::string string;
	
	/// Key-value pair for an INI item.
	typedef std::pair<string, string> INI_ITEM;

	
	/**
	 * @brief A section of the INI file.
	 *
	 * An INI section is composed by a section name, and a group of key-value items.
	 */
	class IniSection 
	{
		friend class IniProfile;
	public:
		string section_name_;				///< The name of this INI section.
		std::vector<INI_ITEM> items_;	///< The items of this section.
	};

public:
	/// The default constructor.
	IniProfile();

	/**
	 * @brief The constructor, from a file.
	 *
	 * Construct an IniProfile object from a file. It calls the LoadConfig() to load the file.
	 * @param profile the file path.
	 */
	IniProfile(const string &profile);

	/// The destructor,doing nothing.
	~IniProfile();

	/**
	 * @brief Load the profile.
	 * 
	 * Load the file, and parse it.
	 * @param profile the file path.
	 * @return 
	 *    true		--succeeded
	 *    false		--failed
	 */
	bool LoadConfig(const string &profile);

	/**
	 * @brief Get an item from the profile.
	 *
	 * Get an item by key and section, and if there are spaces at the head and tail, it won't trim them.
	 * @param section_name	the INI section name.
	 * @param key			the INI item name.
	 * @param value			output the item value.
	 * @return 
	 *    true	--succeeded
	 *    false	--failed
	 */
	bool GetItem(const string &section_name, const string &key, string &value);

	/**
	 * @brief Get an item from the profile, and strip the spaces at head and tail.
	 *
	 * Get an item by key and section, and if there are spaces at the head and tail, it will trim them.
	 * @param section_name	the INI section name.
	 * @param key			the INI item name.
	 * @param value			output the stripped item value.
	 * @return 
	 *    true	--succeeded,
	 *    false	--failed.
	 */
	bool GetTrimedItem(const string &section_name, const string &key, string &value);

	/**
	 * @brief Transform to XML format.
	 *
	 * @param xml_file	the file name of the transformed XML file.
	 */
	void ToXMLFormat(const string &xml_file);
	
	/// Print all the sections, items.
	void Dump();

protected:
	
	/**
	 * @brief Parse a line of the file.
	 * @return 
	 *    VS_OK		--succeeded,
	 *    VS_ERR	--failed.
	 */
	RET_CODE ParseLine(const string &line);

private:
	// The file path.
	string profile_name_;
    
	// The sections and items.
	std::vector<IniSection> ini_sections_;

	// The number of lines that has been read.
	int line_count_;
};

/////////////////////////////////////////////////////////////////////
// Member functions of class IniProfile.
/////////////////////////////////////////////////////////////////////

INLINE
IniProfile::IniProfile()
	: profile_name_(), line_count_(0)
{
}

INLINE
IniProfile::IniProfile(const string &profile)
	: profile_name_(profile), line_count_(0)
{
	LoadConfig(profile_name_);
}

INLINE
IniProfile::~IniProfile()
{
}

INLINE
bool IniProfile::LoadConfig(const string &profile)
{
	ini_sections_.clear();
	line_count_ = 0;
	profile_name_ = profile;

	try
	{
		std::ifstream ifs;
		ifs.open(profile_name_.c_str());
		if (!ifs)	// failed to open file
		{
			return false;
		}

		string line;
		while (getline(ifs, line, '\n'))
		{
			line_count_++;
			switch (ParseLine(line))
			{
			case VS_OK:
				break;
			case VS_ERR:
				std::cout << "Profile Error at line " << line_count_ << " : " << line << std::endl;
				ifs.close();
				return false;
			default:
				break;
			}
		}

		ifs.close();
		return true;
	}
	catch (...)
	{
		std::cout << "Exception happened when parsing the profile" << std::endl;
		return false;
	}
}

INLINE
bool IniProfile::GetItem(const string &section_name, const string &key, string &value)
{
	for (std::vector<IniSection>::iterator iter = ini_sections_.begin();
		iter != ini_sections_.end();
		++iter)
	{
		if (iter->section_name_ == section_name)
		{
			for (std::vector<INI_ITEM>::iterator ini_iter = iter->items_.begin();
				ini_iter != iter->items_.end();
				++ini_iter)
			{
				if (ini_iter->first == key)
				{
					value = ini_iter->second;
					return true;
				}
			}
			return false;
		}
	}
	return false;
}

INLINE
bool IniProfile::GetTrimedItem(const string &section_name, const string &key, string &value)
{
	for (std::vector<IniSection>::iterator iter = ini_sections_.begin();
		iter != ini_sections_.end();
		++iter)
	{
		if (iter->section_name_ == section_name)
		{
			for (std::vector<INI_ITEM>::iterator ini_iter = iter->items_.begin();
				ini_iter != iter->items_.end();
				++ini_iter)
			{
				if (ini_iter->first == key)
				{
					value = StringUtility::Trim(ini_iter->second);
					return true;
				}
			}
			return false;
		}
	}
	return false;
}

INLINE
void IniProfile::Dump()
{
	for (std::vector<IniSection>::iterator iter = ini_sections_.begin();
		iter != ini_sections_.end();
		++iter)
	{
		std::cout <<'[' << iter->section_name_ << "]\n";
		for (std::vector<INI_ITEM>::iterator ini_iter = iter->items_.begin();
			ini_iter != iter->items_.end();
			++ini_iter)
		{
			std::cout << "'" << ini_iter->first << "' = '" << ini_iter->second << "'\n";
		}
	}
}

INLINE
RET_CODE IniProfile::ParseLine(const string &line)
{
	static string strSectionName = "default";

	// todo : should trim left first

	string::size_type nPos = 0;
	if (line[0] == '#')									// comments
	{
		return VS_OK;
	}
	else if (line[0] == '[')							// section name
	{			
		nPos = line.find(']', 1);
		if (nPos == string::npos)
		{
			return VS_ERR;
		}
		else
		{
			string section_name = line.substr(1, nPos-1);
			if (section_name.empty())
			{
				return VS_ERR;
			}
			else
			{
				IniSection iniSection;
				iniSection.section_name_ = section_name;
				strSectionName = section_name;				
				ini_sections_.push_back(iniSection);
				return VS_OK;
			}
		}			
	}
	else if ((nPos=line.find('=')) != string::npos)		// INI item
	{
		string strLeft = line.substr(0, nPos);
		strLeft = StringUtility::Trim(strLeft);
		if (strLeft.empty())
		{
			return VS_ERR;
		}
		else
		{
			string strRight = line.substr(nPos+1);
			if (strRight.empty())
			{
				return VS_ERR;
			}
			else
			{
				for (std::vector<IniSection>::iterator iter = ini_sections_.begin();
					iter != ini_sections_.end();
					++iter)
				{
					if (iter->section_name_ == strSectionName)
					{
						iter->items_.push_back(make_pair(strLeft, strRight));
						break;
					}
				}
				return VS_OK;
			}
		}			
	}
	else if (line.find_first_not_of(" \t\r\n") != string::npos)
	{
		return VS_ERR;
	}
	else															// space line
	{
		return VS_OK;
	}
}

INLINE
void IniProfile::ToXMLFormat(const string &xml_file)
{
	string output  = "<?xml version=\"1.0\" encoding=\"ANSI\"?>\n";
	output += "<Profile>\n";
	for (std::vector<IniSection>::iterator iter = ini_sections_.begin();
		iter != ini_sections_.end();
		++iter)
	{
		output += "\t<" + iter->section_name_ + ">\n";
		for (std::vector<INI_ITEM>::iterator ini_iter = iter->items_.begin();
			ini_iter != iter->items_.end();
			++ini_iter)
		{
			output += "\t\t<" + ini_iter->first;
			output += " value=\"" + ini_iter->second;
			output += "\"/>\n";
		}
		output += "\t</" + iter->section_name_ + ">\n";
	}
	output += "</Profile>\n";

	std::ofstream ofs(xml_file.c_str(), std::ofstream::app);
	ofs.write(output.c_str(), output.length());
}

}

#endif // __VS_INI_PROFILE_H__
